//
// Created by yanhai on 2020/4/6.
//

/**
 * lambda表达式的用法
 * c++11标准新特性
 */

#include <iostream>
#include <functional>

using namespace std;

class Test {
public:
    Test(int a = 0) : a(a) { cout << "Test默认构造" << endl; }

    Test(const Test &obj)
    {
        a = obj.a;
        cout << "Test拷贝构造" << endl;
    }

    ~Test() { cout << "Test析构" << endl; }

    Test &operator=(const Test &obj)
    {
        a = obj.a;
        cout << "Test operator=" << endl;
        return *this;
    }

    int info() const { return a; }

private:
    int a;
};

/**
 * lambda格式
 *      [capture](parameters)mutable->return_type { statement }
 *      [函数对象参数](操作符重载函数参数)mutable->返回值类型 { 函数体 }
 *  1. 函数对象参数：
 *   []: lambda表达式的标志，不可省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。
 *       函数对象参数只能使用那些定义lambda为止时lambda所在作用范围内可见的局部变量（包含lambda所在类的this）。
 *       函数对象参数有以下形式：
 *    1.1 空：没有使用任何函数对象参数。
 *    1.2 =：函数体内可以使用lambda所在作用范围内所有可见的局部变量（包含lambda所在类的this）,
 *           并且是值传递方式（相当于编译器自动为我们按值传递了所有局部变量(这里说的所有变量，是lambda函数体内用到的变量，没有使用的变量是不会被拷贝的)）。
 *    1.3 &：函数体内可以使用lambda所在作用范围内所有可见的局部变量（包含lambda所在类的this）,
 *           并且是引用传递方式（相当于编译器自动为我们按引用传递了所有局部变量）。
 *    1.4 this：函数体内可以使用lambda所在类中的成员变量。
 *    1.5 a：将a按值进行传递。按值进行传递时，函数体内不能修改传递进来的a的拷贝，因为默认情况下函数是const的。
 *           要修改传递进来的a的拷贝，可以添加mutable修饰符。
 *    1.6 &a：将a按引用进行传递。
 *    1.7 a, &b：将a按值传递，b按引用传递
 *    1.8 =, &a, &b：除了a和b按引用进行传递外，其他参数都按值进行传递
 *    1.9 &, a, b：除了a和b按值进行传递外，其他参数都按引用进行传递
 *  2. 操作符重载函数参数 (): 无参时可以省略(当有返回值类型和mutable修饰时，不可省略), 参数可以通过按值（如：(a, b)）和按引用（如：(&a, &b)）两种方式进行传递。
 *  3. 函数返回值  ->返回值类型：标识函数返回值的类型，当返回值为void，或者函数体中只有一处return的地方（此时编译器可以自动推断出返回值类型）时，
 *                           这部分可以省略。
 *  4. 函数体 {}：标识函数的实现，不可省略，但函数体可以为空。
 * @return
 */
void test1()
{
    int a = 10;
    int b = 20;
    Test c;

    // 定义一个lambda函数，并调用
    auto func1 = [] { cout << "我是lambda函数func1" << endl; };
    func1();

    // 也可以在定义处加()直接调用
    [] { cout << "我是lambda函数func2" << endl; }();

    // 在lambda函数中，使用局部变量a
    [a] { cout << "我是lambda函数func3, a的值为: " << a << endl; }();

    // 在lambda函数中，使用所有局部变量，值传递方式
    [=] {
        // a, b, c均是拷贝了一份，并且为const类型
        // c调用了拷贝构造函数
        cout << "我是lambda函数func4, a的值为: " << a
             << ", b的值为： " << b
             << ", c的值为: " << c.info() << endl;
//        a = 100;   // 无法修改a的值
    }();

    // 在lambda函数中，使用所有局部变量，值传递方式
    // 并修改传进来的变量的值
    [=]()mutable {
        // a, b, c均是拷贝了一份，并且为const类型
        // c调用了拷贝构造函数，如果函数体内不使用c，则不会进行拷贝构造
        cout << "我是lambda函数func5, a的值为: " << a
             << ", b的值为： " << b
             << ", c的值为: " << c.info() << endl;
        a = 100;   // 注意：此处修改的是副本a，并不影响外面的a
    }();
    cout << "func5后 a: " << a << endl;

    // 在lambda函数中，使用所有局部变量，引用传递方式
    [&] {
        // a, b, c均是拷贝了一份，并且为const类型
        // c调用了拷贝构造函数
        cout << "我是lambda函数func6, a的值为: " << a
             << ", b的值为： " << b
             << ", c的值为: " << c.info() << endl;
        a = 100;   // 会修改外面的a的值
    }();
    cout << "func6后 a: " << a << endl;

    // 传参，参数按值传递
    [](int a, int b) {
        cout << "我是lambda函数func7, 形参a的值为: " << a
             << ", 形参b的值为： " << b << endl;
    }(1, 2);

    // 传参，参数按引用传递
    [](int &a, int b) {
        cout << "我是lambda函数func8, 形参a的值为: " << a
             << ", 形参b的值为： " << b << endl;
    }(a, 2);

    // 传参，参数按指针传递
    [](int *a, int *b) {
        cout << "我是lambda函数func9, 形参a的值为: " << *a
             << ", 形参b的值为： " << *b << endl;
    }(&a, &b);

    // 返回
    int ret1 = []() { return 10; }();           // 编译器自动推导返回值类型
    int ret2 = []() -> int { return 10; }();    // 明确指定返回值类型
}

/**
 * 使用lambda调用c语言的回调函数时。[]必须为空
 */

int callback1(int a, int b, int (*func)(int, int))
{
    return func(a, b);
}

int callback2(int a, int b, std::function<int(int, int)> func)
{
    return func(a, b);
}

void test2()
{
    int word = 2;
    callback1(20, 10, [](int a, int b) { return a / b; });
//    callback1(20, 10, [=](int a, int b) { return a / b * word; });  // 编译错误，无法捕获其他对象

    callback2(20, 10, [](int a, int b) { return a / b; });
    callback2(20, 10, [=](int a, int b) { return a / b * word; });  // 使用std::function就可以捕获了
}

int main()
{
    test1();
    test2();
    return 0;
}
